/**
 * @FileName: OpenAccountService.java
 * @Author
 * @Description:
 * @Date 2016年5月18日 下午4:27:59
 * @CopyRight ZTE Corporation
 */
package cn.hpclub.server.service;

import java.io.IOException;
import java.util.Date;

import cn.hpclub.server.model.MemberOrg;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.net.MediaType;
import com.jfinal.kit.StrKit;

import cn.hpclub.server.constant.Constant;
import cn.hpclub.server.exception.CheckParamException;
import cn.hpclub.server.model.CmccOrder;
import cn.hpclub.server.model.NetInOrder;
import cn.hpclub.server.util.CallBackData;
import cn.hpclub.server.util.DESAS;
import cn.hpclub.server.util.HttpUtil;
import cn.hpclub.server.util.JointParams;
import cn.hpclub.server.util.Params;
import cn.hpclub.server.util.SystemConfigUtil;
import cn.hpclub.server.util.Utils;

public class OpenAccountService extends BaseCmccService {
    private static final Logger logger = LoggerFactory.getLogger(OpenAccountService.class);
    public final static String RZL_RESULT = "res_code";
    public final static String RZL_MSG = "res_msg";
    public final static String RZL_SUCCESS = "0000000";
    public final static String RZL_CALL_NETWORK_ERROR = "1230321";
    private static final int CMCC_ORDER_OPERATED_DESC_MAX_LENGTH = 512;                                              // cmcc_order的operated_des最大为512

    private String restHostPrefix = SystemConfigUtil.getSystemConfig()
            .getRestHostPrefix();

    private String callBackUrl = SystemConfigUtil.getSystemConfig()
            .getRechargeCallBack();

    /**
     * @param pay_plat TODO
     * @param rawStr
     * @param token
     * @param username
     * @return
     * @throws Exception JSONObject
     * @Title: getJsonFromRawStr
     * @Description:从rawStr中获取并构造请求开户的参数。先用token解析json格式的参数（2. 0客户端采用json格式进行加密）
     * ，
     * 失败则用DPR竖线方式的进行解密和解析
     * （兼容老版本）。
     */
    JSONObject buildJsonFromRawStr(String rawStr, String token, String username) throws Exception {
        try {
            //先尝试用phone号码解密，这是新方案
            return buildJsonFromJsonRawStr(rawStr, username);
        } catch (Exception e) {

            e.printStackTrace();
            logger.error("e:" + e.getMessage());
            logger.warn("parse jsonRawstr failed,to parse jointrawstr");
//            return buildJsonFromJointRawStr(rawStr, username);
            //如果phone解密失败，再用token解密，兼容老方案。但是token解密还是可能失败，是因为单用户多token的情况下，无法
            //确定应该选择哪一个token，这是方案设计的缺陷
            return buildJsonFromJsonRawStr(rawStr, token);
        }
    }

    /**
     * @param rawStr
     * @param payPlat TODO
     * @return
     * @throws Exception JSONObject
     * @Title: getJsonFromJointRawStr
     * @Description:获取竖线拼接的参数串并构建json请求参数，有异常则抛出。也可以不抓异常直接构建，这里抓取异常，增加描述抛出
     */
//    JSONObject buildJsonFromJointRawStr(String rawStr, int payPlat) throws Exception {
//        try {
//            return getJsonFromJointRawStr(rawStr, payPlat);
//        } catch (Exception e) {
//            e.printStackTrace();
//            logger.error("第二次解析rawstr失败：" + e.getMessage());
//            throw new Exception("最终无法解析rawStr:" + e.getMessage());
//        }
//    }

    /**
     * @param rawStr
     * @param token
     * @return
     * @throws Exception JSONObject
     * @Title: getJsonFromJsonRawStr
     * @Description:2.0客户端采用json格式的string进行参数加密 ，解密时，进行json转换后再传给开户接口。
     * 顺带增加一个json格式的校验。
     */
    JSONObject buildJsonFromJsonRawStr(String rawStr, String token) throws Exception {
        DESAS desas = new DESAS(DESAS.generateKey(token));
        String rawJsonStr = desas.decrypt(rawStr);
        // System.out.println("rawJsonStr:" + rawJsonStr);
        JSONObject param = new JSONObject(rawJsonStr);
        return param;
    }

    /**
     * @param rawStr
     * @param payPlat TODO
     * @param token
     * @return
     * @throws Exception JSONObject
     * @Title: getJsonFromJointRawStr
     * @Description:用DPR的密码，解析竖线拼接的字符串。 如果想后台直接通过订单内的数据直接申请开户，
     * 则要解决group_id、regionId和user_group的传值问题
     */
//    JSONObject getJsonFromJointRawStr(String rawStr, int payPlat) throws Exception {
//        JointParams params = new JointParams(Params.DES, DESAS.strDefaultKey, rawStr);
//
//        JSONObject root = new JSONObject();
//
//        JSONObject header = new JSONObject();
//        header.put("ROUTE_PHONE_NO", params.getPara("phone_no"));
//        root.put("HEADER", header); // 路由
//
//        JSONObject body = new JSONObject();
//        body.put("LOGIN_NO", params.getPara("login_no")); // 登陆返回的且经过boss验证过的工号
//        body.put("GROUP_ID", params.getPara("group_id")); // 用户渠道编码，登陆时，从boss获取返回的。大掌柜人员存储的渠道编码仅做了个空判断。？
//        body.put("REGION_ID", params.getPara("region_id")); // 登陆的时候，向boss系统查询返回的regionId？
//        body.put("OP_CODE", "1095");
//        body.put("CUST_FLAG", "0"); // 固定值0
//        body.put("OP_NOTE", "1095");
//        body.put("VERIFY_FLAG", "0"); // 固定值0
//        body.put("CHN_CODE", "96");
//        body.put("ID_TYPE", "1"); // 固定值1
//        body.put("ID_ICCID", params.getPara("id_iccid")); // 身份证号码
//        body.put("SEX_CODE", params.getPara("sex_code")); // 男11，女12
//        body.put("CUST_NAME", params.getPara("cust_name")); // 身份证姓名
//        body.put("CUST_ADDRESS", params.getPara("cust_address")); // 身份证地址
//        body.put("ID_ADDRESS", params.getPara("id_address")); // 目前也传的身份证地址
//
//        body.put("ID_VALIDDATE", getValidDate(params.getPara("id_validdate"))); // 身份证到期日期，20170211格式
//
//        body.put("TYPE_CODE", "1"); // 固定值1
//        body.put("PHONE_NO", params.getPara("phone_no")); // 开户电话号码
//        body.put("SIM_NO", params.getPara("sim_no")); // 白卡卡号
//        body.put("USERGROUP_FLAG", "3"); // 固定值3
//        body.put("CHNN_ID", "96");
//        body.put("USER_GROUP", params.getPara("user_group")); // 暂时不确定客户端怎么传的？
//        body.put("OUT_ACCEPT", params.getPara("out_accept")); // 订单号
//
//        JSONObject feeInfo = new JSONObject();
//        feeInfo.put("SIM_FEE", params.getPara("sim_fee")); // 客户端传固定值0
//        feeInfo.put("PREPAY_FEE", params.getPara("prepay_fee")); // 所选套餐的预存金额，也即订单金额和支付金额（目前不支持分期），单位为分。
//        feeInfo.put("RECEIVE_FEE_TYPE", "96"); // 固定值96
//
//        switch (payPlat) {
//            case Constant.PayPlat.CCB:
//                feeInfo.put("PAY_TYPE", "JH"); // 支付方式
//                break;
//            case Constant.PayPlat.ALIPAY:
//                feeInfo.put("PAY_TYPE", "H");
//                break;
//            case Constant.PayPlat.UNION_PAY:
//                feeInfo.put("PAY_TYPE", "YL");
//                break;
//            default:
//                throw new Exception("支付方式有误:" + payPlat);
//        }
//
//        body.put("FEE_INFO", feeInfo);
//
//        JSONObject productList = new JSONObject();
//        JSONObject product = new JSONObject();
//        product.put("PROD_ID", "");
//        JSONObject prodprcInfo = new JSONObject();
//        prodprcInfo.put("PROD_PRCID", params.getPara("prod_prcid")); // 套餐的资费代码
//        prodprcInfo.put("PRODPRCATTR_LIST", new JSONObject());
//        prodprcInfo.put("SVCATTR_LIST", new JSONObject());
//
//        JSONObject prodprc = new JSONObject();
//        prodprc.put("DEVELOP_NO", "");
//
//        prodprcInfo.put("PRODPRC", prodprc);
//
//        product.put("PRODPRC_INFO", prodprcInfo);
//
//        productList.put("PRODUCT", product);
//        body.put("PRODUCT_LIST", productList);
//
//        root.put("BODY", body);
//
//        // logger.info("root:" + root);
//
//        return root;
//    }
    private String getValidDate(String date) throws Exception {
        if (StrKit.isBlank(date)) {
            throw new Exception("身份证日期格式不正确：" + date);
        }

        date = date.replace("年", "").replace("月", "").replace("日", "");
        date = date.replace(".", "").replace("-", "").replace(" ", "");
        if ("长期".equals(date)) {
            date = "20990101";
        }
        // System.out.println("date:" + date);
        return date;

    }

    /**
     * @param order
     * @param orderId
     * @param username
     * @return
     * @throws CheckParamException JSONObject
     * @Title: getJsonParam
     * @Description:判断参数合法性的同时，获取参数。要么抛异常，要么返回json数据
     */
    JSONObject buildJsonParam(CmccOrder order, String orderId, String token, String username) throws CheckParamException {

        try {
            String rawStr = order.getStr(CmccOrder.RAW_STR);
            // logger.info("order:" + orderId + ",rawStr to request:" + rawStr);
            if (StrKit.isBlank(rawStr)) {
                logger.error("rawstr is empty." + orderId);
                throw new Exception("rawstr is empty.");
            }

//            int payPlat = order.getInt(CmccOrder.PAY_PLAT);
//            return buildJsonFromRawStr(rawStr, token, payPlat);
            return buildJsonFromRawStr(rawStr, token, username);
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("参数异常：" + orderId + ":" + e.getMessage());
            throw new CheckParamException(e.getMessage());
        }

    }

    /**
     * @param orderId
     * @return
     * @throws Exception boolean
     * @Title: openAccount
     * @Description:办理开户，对于明确返回错误的，执行退费。不存在的订单，任务丢弃
     */
    public void openAccountAndRefund(String orderId) {
        // logger.info("openAccount:" + orderId);
        CmccOrder order = null;
        try {

            // 订单不存在的情况下，不抓异常，抓了也无法找到对应的order去记录异常情况
            order = CmccOrder.dao.getOrderInfo(orderId);
            if (order == null) {
                logger.error("order does not exist:" + orderId);
                return;
            }
            // 过滤掉未支付的订单
            if (order.getInt(CmccOrder.PAY_STATUS) != CmccOrder.PAID) {
                logger.error("order is not paid." + orderId);
                return;
            }

            openAccount(orderId, order);
        } catch (OpenAccountFailedException e) {
            // 办理网络异常
            // logger.error("OpenAccountFailedException:" + e.getMessage());
            logger.error("OpenAccountFailedException:" + orderId);
            // 只有openAccount会抛这个异常，order是有值的
            // 对于明确返回失败的订单，执行 自动退费，并更新退费状态。
            // logger.info("orderId:" + orderId + ",to refund:" +
            // order.getDouble(CmccOrder.PAY_AMOUNT));
            CcbService.service.appRefund(orderId, String.valueOf(order.getDouble(CmccOrder.PAY_AMOUNT)));
        } catch (Exception e) {
            logger.error("openAccount,exception,orderid:" + orderId);
            e.printStackTrace();
        }

    }

    /**
     * @param orderId
     * @param order   void
     * @Title: openAccount
     * @Description:构造请求，请求并解析开户办理结果
     */

    private void openAccount(String orderId, CmccOrder order) throws OpenAccountFailedException {
        String r = "";
        try {
            // 改用userid加解密，token再单用户多设备情况下无法准确查出
            String username = MemberOrg.dao.findById(order.getInt(CmccOrder.HANDLER_USER_ID)).getStr(MemberOrg.USER_NAME);
            logger.info("usrname =" + username);
            // token非法，是系统紊乱才会造成，因此，可以走退款流程。若支付是未支付，是不能退款成功的。
            String token = getToken(orderId, order);
            // 构造请求参数
            JSONObject param = buildJsonParam(order, orderId, token, username);

            // 发起办理业务请求
            r = requestOpenAccount(param);
            // logger.info("r:" + r);

            // 解析请求结果，返回结果格式转换，若结构不对，则进入异常。
            parseResult(orderId, r);

        } catch (CheckParamException e) {
            // 此种异常，并未发起业务办理。
            e.printStackTrace();
            logger.error("参数非法,未发起办理：" + e.getMessage() + ":" + orderId);
            // 此处异常msg不确定，截取长度128
            updateCmccOrderStatus(orderId, "", CmccOrder.ORDER_NOT_REQUEST, "参数非法,未发起办理：" + e.getMessage(), new Date(),
                    "");
            // 发起退费
            throw new OpenAccountFailedException(e.getMessage());
        } catch (IOException e) {
            // 办理网络异常，此种异常，已经发起业务办理，但没有得到返回，业务办理可能成功过了也可能失败了。
            logger.error("IOException:" + e.getMessage());
            updateCmccOrderStatus(orderId, "", CmccOrder.ORDER_REQUEST_NOT_BACK, "请求网络异常：" + e.getMessage(), new Date(),
                    "");
            // 如果网络超时需要自动退款，则在此处抛出OpenAccountFailedException异常
            // throw new OpenAccountFailedException(e.getMessage());

        } catch (OpenAccountFailedException e) {
            // 有明确错误码的开户失败，抛出去进行退费。orderstatus在异常之前已经更新。这种异常，已经发起业务办理并得到反馈
            logger.error("OpenAccountFailedException:" + e.getMessage());
            updateCmccOrderStatus(orderId, "", CmccOrder.ORDER_FAILED, e.getMessage(), new Date(), "");
            throw new OpenAccountFailedException(e.getMessage());
        } catch (ParseResultException e) {
            e.printStackTrace();
            logger.error("办理结果解析异常：" + e.getMessage());
            // 这种异常，为解析失败异常，业务已经办理，但是返回的r可能出错，如JSON异常，如NullPointer异常.业务办理状态可能成功，也可能失败,与IOEXCEPTION类似
            updateCmccOrderStatus(orderId, "", CmccOrder.ORDER_REQUEST_RESULT_PARSE_ERROR,
                    "办理结果解析异常：" + r + ":" + e.getMessage(), new Date(), "");
        }

    }

    public void saveSalesPromotion(String orderId, String cmccOrderId, String returnMsg) {
        try {

            String xml = buildXmlParam(orderId, cmccOrderId, returnMsg);
            // logger.info("callBackUrl:" + callBackUrl);
            // logger.info("xml:" + xml);
            String result = HttpUtil.syncPostJsonBody(callBackUrl, MediaType.APPLICATION_XML_UTF_8, xml, 30000);
            // logger.info("result:" + result);
            if (!"success".equals(result)) {
                logger.error("api callback return failed:" + result);
            }

        } catch (Exception e) {
            e.printStackTrace();
            logger.error("xhrw saveSalesPromotion exception:" + e.getMessage());

        }
    }

    private String buildXmlParam(String orderId, String cmccOrderId, String returnMsg) throws Exception {
        CmccOrder order = CmccOrder.dao.getOrderInfo(orderId);
        if (order == null) {
            logger.error("order does not exist:" + orderId);
            throw new Exception("订单号不存在，无法获取订单信息进行回调" + orderId);
        }

        // 其值为套餐的预存金额，该只由服务器传给客户端套餐的advancedeposit_rates
        // Package pkg =
        // Package.dao.getPackage(order.getInt(CmccOrder.PACKAGE_ID));
        // if(pkg == null){
        // logger.error("package not exist:" + orderId);
        // throw new Exception("套餐不存在，无法获取订单信息进行回调" + orderId);
        // }
        // String prePayFee =
        // String.valueOf(Double.valueOf((pkg.getDouble(Package.PREDEPOSITED) *
        // 100)).intValue());
        String prePayFee = String.valueOf(Double.valueOf((order.getDouble(CmccOrder.PAY_AMOUNT) * 100)).intValue());
        logger.info("prePayFee:" + prePayFee);
        // 成功才会调用，所以return_code为0
        String moduleCode = type2ModuleCode(order.getInt(CmccOrder.TYPE));

        CallBackData callBackData = new CallBackData.Builder().order_id(cmccOrderId).thirdpart_id(orderId)
                .business_code(order.getStr(CmccOrder.TARIFF_CODE)).isUnreg("0").moduleId("3").moduleCode(moduleCode)
                .methodName("sPubOpenUserZxt").phone_no(order.getStr(CmccOrder.PHONE_NUMBER)).return_code("0")
                .return_msg(returnMsg).amount(prePayFee)
                .storeid(String.valueOf(order.getInt(NetInOrder.HANDLER_ORG_ID)))
                .userid(String.valueOf(order.getInt(CmccOrder.HANDLER_USER_ID)))
                .business_name(order.getStr(CmccOrder.PACKAGE_NAME)).build();// 1.5.9版本新增business_name

        return callBackData.toXml();

    }

    private String type2ModuleCode(int type) {

        // 普通选号入网、营销活动选号入网
        if (type == 0) {
            return "1095";
        } else if (type == 1) {
            // 全号段选号入网
            return "4312";
        }
        logger.warn("cmcc order error type:" + type);
        // 其他的，给普通的值
        return "1095";
    }

    /**
     * @param orderId
     * @param r
     * @throws OpenAccountFailedException void
     * @throws IOException
     * @Title: parseResult
     * @Description:TODO
     */

    private void parseResult(String orderId, String r)
            throws ParseResultException, OpenAccountFailedException, IOException {
        try {
            // r = "{\"ROOT\":
            // {\"RETURN_CODE\":0,\"RETURN_MSG\":\"ok!\",\"USER_MSG\":\"处理成功!\",\"DETAIL_MSG\":\"OK!\",\"PROMPT_MSG\":\"\",\"OUT_DATA\":{\"ORDER_ID\":\"O1117010900487024\",\"CREATE_ACCEPT\":2016619237874,\"ID_NO\":11311041645923,\"PHONE_NO\":\"15756534335\",\"MASTER_SERV_ID\":\"1001\"}}}";
            JSONObject result = new JSONObject(r);
            JSONObject root = result.getJSONObject("ROOT");

            if (root.getInt("RETURN_CODE") == 0) {
                JSONObject outData = root.getJSONObject("OUT_DATA");
                String cmccOrderId = outData.getString("ORDER_ID");
                if (cmccOrderId == null) {
                    cmccOrderId = "";
                }
                // 业务办理成功，updateCmccOrderStatus不抛异常
                updateCmccOrderStatus(orderId, cmccOrderId, CmccOrder.ORDER_SUC, result.toString(), new Date(),
                        "首次办理成功");
                // save salepromotion办理成功了，则同步到移动业务清单报表里面去。不抛异常，抛了也无法处理
                saveSalesPromotion(orderId, cmccOrderId, root.getString("RETURN_MSG"));
            } else {
                // 返回5000且有RETURN_FLAG字段，表示请求时rest或者rpc发生IOException，不能退费;
                // 返回5000没有RETURN_FLAG字段，表示是BOSS端返回的有明确错误码的错误，可以退费
                if (root.getInt("RETURN_CODE") == 5000 && root.has("RETURN_FLAG")
                        && "dzg".equals(root.getString("RETURN_FLAG"))) {// 请求时rest或者rpc发生IOException时不能退费
                    logger.error(orderId + " ： rest或者rpc进行http请求时发生IOException，不需要退费：" + result.toString());
                    throw new IOException("办理业务失败，请求时rest或者rpc发生IOException：" + result.toString());
                } else {
                    throw new OpenAccountFailedException("办理业务失败，有明确错误码：" + result.toString());
                }
            }
        } catch (OpenAccountFailedException e) {
            e.printStackTrace();
            logger.error("r返回错误码：" + e.getMessage());
            throw new OpenAccountFailedException(e.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
            logger.error("r返回错误码：" + e.getMessage());
            throw new IOException(e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("r解析异常：" + e.getMessage());
            throw new ParseResultException("r异常:" + r + ":" + e.getMessage());
        }

    }

    /**
     * @param orderId
     * @param order
     * @return
     * @throws CheckParamException String
     * @Title: getToken
     * @Description:获取并校验token
     */

    private String getToken(String orderId, CmccOrder order) throws CheckParamException {
        try {
            // token的校验放在此处，未放入getJsonParam，因requestOpenAccount需要使用
            String token = TokenService.service.getTokenByUserid(order.getInt(CmccOrder.HANDLER_USER_ID));
            // logger.info("token:" + token);
            // 仅判断合法性，不判断时间有效性，因为该方法可能隔天或未来作为重复业务办理调用
            if (!TokenService.service.isTokenLegal(token)) {
                logger.error("token参数非法:" + token + ":" + orderId);
                throw new Exception("非法token:" + token);
            }
            return token;

        } catch (Exception e) {
            e.printStackTrace();
            logger.error("token exception:" + e.getMessage());
            throw new CheckParamException("token异常:" + e.getMessage());
        }

    }

    /**
     * @param orderId
     * @param orderStatus
     * @param desc
     * @param time
     * @return boolean
     * @Title: updateCmccOrderStatus
     * @Description:更新订单的办理状态，捕获并忽略异常
     */
    public boolean updateCmccOrderStatus(String orderId, String cmccOrderId, int orderStatus, String desc, Date time,
                                         String sucDesc) {
        try {
            return CmccOrder.dao.updateOrderStatus(orderId, cmccOrderId, orderStatus,
                    Utils.getShortMsg(desc, CMCC_ORDER_OPERATED_DESC_MAX_LENGTH), time,
                    sucDesc);
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("更新数据库异常：" + orderId + ":" + orderStatus + ":"
                    + Utils.getShortMsg(desc, CMCC_ORDER_OPERATED_DESC_MAX_LENGTH) + ":" + time);
        }
        return false;

    }

    /**
     * @param param
     * @return
     * @throws IOException String
     * @Title: sendOpenAccountRequest
     * @Description:发起开户业务办理请求，任务异常均返回IO异常到上一级统一处理.要么返回result，要么返回异常
     */
    public String requestOpenAccount(JSONObject param) throws IOException {

        // logger.info("request param:" + param.toString());
        return HttpUtil.syncPostJsonBody(restHostPrefix + Constant.Url.CMCC_OPEN_ACCOUNT, MediaType.JSON_UTF_8,
                param.toString(), 30000);
    }

    /**
     * 开户接口返回明确错误码时抛的异常
     */
    public static class OpenAccountFailedException extends Exception {

        private static final long serialVersionUID = 6780702742548362482L;

        public OpenAccountFailedException(String message) {
            super(message);
        }
    }

    public static class ParseResultException extends Exception {

        private static final long serialVersionUID = -8569537874668851030L;

        public ParseResultException(String message) {
            super(message);
        }
    }

}
